package com.hexacta.hrs.repositories.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.DistinctRootEntityResultTransformer;
import org.hibernate.transform.ResultTransformer;
import org.vectrics.employee.Employee;

import com.hexacta.hrs.domain.employee.history.MentorAssignment;
import com.hexacta.hrs.domain.feedback.Feedback;
import com.hexacta.hrs.domain.mentoring.Meeting;
import com.hexacta.hrs.domain.mentoring.MeetingStatus;
import com.hexacta.hrs.repositories.MentoringRecordObjectRepository;

/**
 * 
 * @author ncortinez
 * 
 */
public class MentoringRecordObjectRepositoryImpl extends
		HibernateRecordObjectRepository<Employee> implements
		MentoringRecordObjectRepository {

	/** {@inheritDoc} */
	public MentoringRecordObjectRepositoryImpl(final Class<Employee> entityClass) {
		super(entityClass);
	}

	/** {@inheritDoc} */
	@SuppressWarnings("unchecked")
	@Override
	public Collection<Employee> matching(final Employee criteria) {
		Criteria crit = getSession().createCriteria(Employee.class);

		// Filtro por empleado
		if (criteria.getId() != null) {
			crit.add(Restrictions.eq("id", criteria.getId()));
		}

		// Filtro por mentor
		if (criteria.getMentor() != null
				&& criteria.getMentor().getId() != null) {
			crit
					.add(Restrictions.eq("mentor.id", criteria.getMentor()
							.getId()));
		}

		// Solo los que tienen mentores
		crit.add(Restrictions.isNotNull("mentor"));

		// Filtro por la fecha de la meeting
		List<Employee> matchEmployee = crit.list();

		return matchEmployee;
	}

	/** {@inheritDoc} */
	@SuppressWarnings("unchecked")
	public List<Object[]> search(final Map<String, Object> parameters) {

		String critLastMeeting = createCriteriaLastMeeting(parameters);
		String critNextMeeting = createCriteriaNextMeeting(parameters);

		// TODO: bug cuando hay mas de una relacion entre un mentor y mentorado
		// y la primer relacion no tiene meetings y la segunda si. Hay que
		// definir de que relacion son las meetings definiendo el periodo

		// Consulta general
		String hql = " select e, ma, m from " + Employee.class.getName()
				+ " as e "
				+ " join e.employmentConditionHistory.mentorAssignments as ma "
				+ " ,  " + Meeting.class.getName() + " as m ";

		if (parameters.get("month") != null) {
			hql += ",  " + Meeting.class.getName() + " as mNext ";
		}

		// Filtro por last meeting
		if (parameters.get("dateFrom") != null
				|| parameters.get(LAST_PERFORMED_MEETING_BEFORE_THAN) != null) {
			hql += ",  " + Meeting.class.getName() + " as mLast ";
		}

		hql += " where (e.id = m.employee.id and ma.mentor.id = m.mentor.id) "
				+ "  and  m.date = (select  max(m1.date) from "
				+ Meeting.class.getName()
				+ " as m1 "
				+ "                 where (e.id = m1.employee.id and ma.mentor.id = m1.mentor.id) "

				// Solo las meetings del periodo valido de la relacion
				// mentor-mentorado
				// + "	and exists( select min(nextAssignment.effectiveDate) "
				// + "		from  " + Employee.class.getName() +
				// " as e1 join e1.employmentConditionHistory.mentorAssignments as nextAssignment"
				// +
				// "		where nextAssignment.effectiveDate > ma.effectiveDate and e1.id = e.id) "
				//        
				+ "                 and  m1.date >= ma.effectiveDate   "

				// En el caso que el mentor y mentorado tengan 2 periodos de
				// mentorado
				// se filtra que la meeting este en el periodo que corresponde.
				// +
				// "                 and  m1.date <=  ( select min(case when ma1 = ma then current_timestamp() + year(99) else ma1.effectiveDate end) from "
				// + Employee.class.getName()
				// + " as e1 "
				// +
				// "                                            join e1.employmentConditionHistory.mentorAssignments as ma1                "
				// +
				// "                                           where (ma1.effectiveDate >= ma.effectiveDate) "
				// +
				// "                                             and e1.id = e.id  "
				// +
				// "                                       and ma1.mentor.id = ma.mentor.id   "
				// + "                                   )            "

				+ "                ) " + critNextMeeting + critLastMeeting;
		
		//Filtro por mentores activos
		if (parameters.get("onlyActiveMentor") != null) {
			Boolean onlyActiveMentor= (Boolean) parameters
					.get("onlyActiveMentor");
			if (onlyActiveMentor) {
				hql += " and e.mentor.dateEnd is null ";
			}
		}
		//Filtro por empleados activos
		if (parameters.get("onlyActiveEmployees") != null) {
			Boolean onlyActiveEmployees= (Boolean) parameters
					.get("onlyActiveEmployees");
			if (onlyActiveEmployees) {
				hql += " and e.dateEnd is null ";
				
			}
		}
	
		// Filtro por empleado
		if (parameters.get("mentee") != null) {
			hql += " and e.id = :employeeId ";
		}

		// Filtro por mentor
		if (parameters.get("mentor") != null) {
			hql += " and ma.mentor.id = :mentorId ";
		}
		
		hql += createHqlOnlyCurrentRelationships(parameters);
		
		// Ordeno por empleado y luego por fecha de relacion
		hql += " order by e.id asc,  ma.effectiveDate desc ";
		

		// Creo la query a partir del hql
		Query query = this.getSession().createQuery(hql);

		if (parameters.get("mentee") != null) {
			query.setLong("employeeId", (Long) parameters.get("mentee"));
		}
		if (parameters.get("mentor") != null) {
			query.setLong("mentorId", (Long) parameters.get("mentor"));
		}
		if (parameters.get("dateFrom") != null) {
			query.setDate("dFrom", (Date) parameters.get("dateFrom"));
			query.setString("lastM", MeetingStatus.PERFORMED.getName());
		}
		if (parameters.get(LAST_PERFORMED_MEETING_BEFORE_THAN) != null) {
			query.setDate("dTo", (Date) parameters
					.get(LAST_PERFORMED_MEETING_BEFORE_THAN));
			query.setString("lastM", MeetingStatus.PERFORMED.getName());
		}
		if (parameters.get("month") != null) {
			query.setInteger("month", (Integer) parameters.get("month") + 1);
			query.setString("nextM", MeetingStatus.SCHEDULED.getName());
		}

		List<Object[]> result = new ArrayList<Object[]>(query.list());

		if (parameters.get("dateFrom") == null
				&& parameters.get(LAST_PERFORMED_MEETING_BEFORE_THAN) == null
				&& parameters.get("month") == null) {
			result.addAll(searchWithoutMeeting(parameters));
		}

		// Obtengo los resultados
		return result;
	}

	private String createCriteriaNextMeeting(
			final Map<String, Object> parameters) {
		String critNextMeeting = "";

		// Filtro por next meeting
		if (parameters.get("month") != null) {
			critNextMeeting += " and (e.id = mNext.employee.id and ma.mentor.id = mNext.mentor.id) and mNext.date = (select max(mN.date) from "
					+ Meeting.class.getName()
					+ " as mN where (e.id = mN.employee.id and ma.mentor.id = mN.mentor.id) "
					+ " and month(mN.date) = :month and mN.status = :nextM ) ";
		}
		return critNextMeeting;
	}

	private String createCriteriaLastMeeting(
			final Map<String, Object> parameters) {
		String critLastMeeting = "";

		// Filtro por last meeting
		if (parameters.get("dateFrom") != null
				|| parameters.get(LAST_PERFORMED_MEETING_BEFORE_THAN) != null) {

			critLastMeeting += " and (e.id = mLast.employee.id and ma.mentor.id = mLast.mentor.id) "
					+ " and mLast.date = (select max(mL.date) from "
					+ Meeting.class.getName()
					+ " as mL where (e.id = mL.employee.id and ma.mentor.id = mL.mentor.id) "
					+ " and mL.status = :lastM ";

			if (parameters.get("dateFrom") != null) {
				critLastMeeting += "  and mL.date >= :dFrom ";
			}
			if (parameters.get(LAST_PERFORMED_MEETING_BEFORE_THAN) != null) {
				critLastMeeting += "  and mL.date <= :dTo  ";
			}
			critLastMeeting += " ) ";
		}
		return critLastMeeting;
	}

	private Collection<Object[]> searchWithoutMeeting(
			final Map<String, Object> parameters) {

		// Consulta general
		String hqlWithoutMeeting = " select e, ma from "
				+ Employee.class.getName()
				+ " as e "
				+ " join e.employmentConditionHistory.mentorAssignments as ma "
				+ " where e.id not in (select m1.employee.id from "
				+ Meeting.class.getName()
				+ " as m1 "
				+ "                 where (e.id = m1.employee.id and ma.mentor.id = m1.mentor.id) "
				+ "                 and  m1.date >= ma.effectiveDate "
				+ "                    ) ";

		// Filtro por empleado
		if (parameters.get("mentee") != null) {
			hqlWithoutMeeting += " and e.id = :employeeId ";
		}

		// Filtro por mentor
		if (parameters.get("mentor") != null) {
			hqlWithoutMeeting += " and ma.mentor.id = :mentorId ";
		}
		//Filtro por mentores activos
		if (parameters.get("onlyActiveMentor") != null) {
			Boolean onlyActiveMentor= (Boolean) parameters
					.get("onlyActiveMentor");
			if (onlyActiveMentor) {
				hqlWithoutMeeting += " and e.mentor.dateEnd is null ";
			}
		}
		//Filtro por empleados activos
		if (parameters.get("onlyActiveEmployees") != null) {
			Boolean onlyActiveEmployees= (Boolean) parameters
					.get("onlyActiveEmployees");
			if (onlyActiveEmployees) {
				hqlWithoutMeeting += " and e.dateEnd is null ";
				
			}
		}
		
		hqlWithoutMeeting += createHqlOnlyCurrentRelationships(parameters);

		// Ordeno por empleado y luego por fecha de relacion
		hqlWithoutMeeting += " order by e.id asc, ma.effectiveDate desc ";

		// Creo la query a partir del hqlWithoutMeeting
		Query queryWithoutMeeting = this.getSession().createQuery(
				hqlWithoutMeeting);

		if (parameters.get("mentee") != null) {
			queryWithoutMeeting.setLong("employeeId", (Long) parameters
					.get("mentee"));
		}
		if (parameters.get("mentor") != null) {
			queryWithoutMeeting.setLong("mentorId", (Long) parameters
					.get("mentor"));
		}

		// Obtengo los resultados
		return queryWithoutMeeting.list();
	}

	/**
	 * @param parameters
	 * @return
	 */
	private String createHqlOnlyCurrentRelationships(
			final Map<String, Object> parameters) {
		String hqlOnlyCurrentRelationships = "";

		if (parameters.get("onlyCurrentRelationships") != null) {
			Boolean onlyCurrentRelationships = (Boolean) parameters
					.get("onlyCurrentRelationships");

			if (onlyCurrentRelationships) {
				hqlOnlyCurrentRelationships += " and ma.mentor.id = e.mentor.id  "
						+ " and ma.effectiveDate = (select max(ma1.effectiveDate) from "
						+ Employee.class.getName()
						+ " as e1 join e1.employmentConditionHistory.mentorAssignments as ma1 "
						+ " where ma1.mentor.id = e1.mentor.id "
						+ " and e1.id = e.id "
						+ " and e.mentor.id = e1.mentor.id ) ";
			}
		}
		return hqlOnlyCurrentRelationships;
	}

	@Override
	public Collection<Employee> allMentors() {
		Criteria crit = getSession().createCriteria(MentorAssignment.class,
				"assignment");
		List<MentorAssignment> assignements = crit.list();
		Collection<Employee> result = CollectionUtils.collect(assignements,
				new MentorAssignementToEmployeeTransformer());
		Set<Employee> mentors = new HashSet<Employee>(result);
		return mentors;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.hexacta.hrs.repositories.MentoringRecordObjectRepository#allActiveMentors
	 * ()
	 */
	@Override
	public Collection<Employee> allActiveMentors() {

		Query query = getSession().createQuery("SELECT DISTINCT ma.mentor FROM " + MentorAssignment.class.getName() + " ma " +
				"WHERE ma.mentor.dateEnd IS NULL");
		return query.list();
	}

}

class MentorAssignementToEmployeeTransformer implements Transformer {

	@Override
	public Object transform(Object input) {
		MentorAssignment assignement = (MentorAssignment) input;
		return assignement.getMentor();
	}

}
